{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "navG4j6eqc_o"
      },
      "source": [
        "##### Copyright 2025 Google LLC."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "metadata": {
        "cellView": "form",
        "id": "0lpR41ozqBFp"
      },
      "outputs": [],
      "source": [
        "# @title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "k9g4WhqtqiV-"
      },
      "source": [
        "# Gemini API: Context Caching Quickstart\n",
        "\n",
        "<a target=\"_blank\" href=\"https://colab.research.google.com/github/google-gemini/cookbook/blob/main/quickstarts/Caching.ipynb\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" height=30/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WCC-LFNtqogI"
      },
      "source": [
        "This notebook introduces context caching with the Gemini API and provides examples of interacting with the Apollo 11 transcript using the Python SDK. For a more comprehensive look, check out [the caching guide](https://ai.google.dev/gemini-api/docs/caching?lang=python)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "857d8bf104ed"
      },
      "source": [
        "### Install dependencies"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "metadata": {
        "id": "5yRLvyPhrXSf"
      },
      "outputs": [],
      "source": [
        "%pip install -q -U \"google-genai>=1.0.0\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xrJYxEpOrc3d"
      },
      "source": [
        "### Configure your API key\n",
        "\n",
        "To run the following cell, your API key must be stored it in a Colab Secret named `GOOGLE_API_KEY`. If you don't already have an API key, or you're not sure how to create a Colab Secret, see [Authentication ![image](https://storage.googleapis.com/generativeai-downloads/images/colab_icon16.png)](../quickstarts/Authentication.ipynb) for an example."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {
        "id": "EgO56yWoriI0"
      },
      "outputs": [],
      "source": [
        "from google.colab import userdata\n",
        "from google import genai\n",
        "\n",
        "GOOGLE_API_KEY = userdata.get('GOOGLE_API_KEY')\n",
        "client = genai.Client(api_key=GOOGLE_API_KEY)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2T-ZorTqrsoz"
      },
      "source": [
        "## Upload a file\n",
        "\n",
        "A common pattern with the Gemini API is to ask a number of questions of the same document. Context caching is designed to assist with this case, and can be more efficient by avoiding the need to pass the same tokens through the model for each new request.\n",
        "\n",
        "This example will be based on the transcript from the Apollo 11 mission.\n",
        "\n",
        "Start by downloading that transcript."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "metadata": {
        "id": "Sa-r2s_ltBXy"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "INTRODUCTION\n",
            "\n",
            "This is the transcription of the Technical Air-to-Ground Voice Transmission (GOSS NET 1) from the Apollo 11 mission.\n",
            "\n",
            "Communicators in the text may be identified according to the following list.\n",
            "\n",
            "Spacecraft:\n",
            "CDR\tCommander\tNeil A. Armstrong\n",
            "CMP\tCommand module pilot   \tMichael Collins\n",
            "LMP\tLunar module pilot\tEdwin E. ALdrin, Jr.\n"
          ]
        }
      ],
      "source": [
        "!wget -q https://storage.googleapis.com/generativeai-downloads/data/a11.txt\n",
        "!head a11.txt"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "014697858670"
      },
      "source": [
        "Now upload the transcript using the [File API](../quickstarts/File_API.ipynb)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "metadata": {
        "id": "b3bU9AvcvZ_x"
      },
      "outputs": [],
      "source": [
        "document = client.files.upload(file=\"a11.txt\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "N4LP7unAvmce"
      },
      "source": [
        "## Cache the prompt\n",
        "\n",
        "Next create a [`CachedContent`](https://ai.google.dev/api/python/google/generativeai/protos/CachedContent) object specifying the prompt you want to use, including the file and other fields you wish to cache. In this example the [`system_instruction`](../quickstarts/System_instructions.ipynb) has been set, and the document was provided in the prompt.\n",
        "\n",
        "Note that caches are model specific. You cannot use a cache made with a different model as their tokenization might be slightly different."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "metadata": {
        "id": "55V-QkaWv4tb"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "CachedContent(\n",
              "  create_time=datetime.datetime(2025, 8, 6, 13, 48, 36, 419118, tzinfo=TzInfo(UTC)),\n",
              "  display_name='',\n",
              "  expire_time=datetime.datetime(2025, 8, 6, 14, 48, 36, 38936, tzinfo=TzInfo(UTC)),\n",
              "  model='models/gemini-2.5-flash',\n",
              "  name='cachedContents/0c5j38gpopx49ok6x7kedvbpy65d1bzkq8i5vldr',\n",
              "  update_time=datetime.datetime(2025, 8, 6, 13, 48, 36, 419118, tzinfo=TzInfo(UTC)),\n",
              "  usage_metadata=CachedContentUsageMetadata(\n",
              "    total_token_count=322698\n",
              "  )\n",
              ")"
            ]
          },
          "execution_count": 28,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "MODEL_ID = \"gemini-2.5-flash\" # @param [\"gemini-2.5-flash-lite\", \"gemini-2.5-flash\", \"gemini-2.5-pro\",\"gemini-3-pro-preview\"] {\"allow-input\":true, isTemplate: true}\n",
        "\n",
        "apollo_cache = client.caches.create(\n",
        "    model=MODEL_ID,\n",
        "    config={\n",
        "        'contents': [document],\n",
        "        'system_instruction': 'You are an expert at analyzing transcripts.',\n",
        "    },\n",
        ")\n",
        "\n",
        "apollo_cache"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "metadata": {
        "id": "50kG3mJn50WM"
      },
      "outputs": [
        {
          "data": {
            "text/markdown": [
              "As you can see in the output, you just cached **322698** tokens."
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from IPython.display import Markdown\n",
        "\n",
        "display(Markdown(f\"As you can see in the output, you just cached **{apollo_cache.usage_metadata.total_token_count}** tokens.\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9j5yzay5xyPC"
      },
      "source": [
        "## Manage the cache expiry\n",
        "\n",
        "Once you have a `CachedContent` object, you can update the expiry time to keep it alive while you need it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "metadata": {
        "id": "cUJT2ESUyTGb"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "CachedContent(\n",
              "  create_time=datetime.datetime(2025, 8, 6, 13, 48, 36, 419118, tzinfo=TzInfo(UTC)),\n",
              "  display_name='',\n",
              "  expire_time=datetime.datetime(2025, 8, 6, 15, 48, 36, 651814, tzinfo=TzInfo(UTC)),\n",
              "  model='models/gemini-2.5-flash',\n",
              "  name='cachedContents/0c5j38gpopx49ok6x7kedvbpy65d1bzkq8i5vldr',\n",
              "  update_time=datetime.datetime(2025, 8, 6, 13, 48, 36, 691886, tzinfo=TzInfo(UTC)),\n",
              "  usage_metadata=CachedContentUsageMetadata(\n",
              "    total_token_count=322698\n",
              "  )\n",
              ")"
            ]
          },
          "execution_count": 30,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from google.genai import types\n",
        "\n",
        "client.caches.update(\n",
        "    name=apollo_cache.name,\n",
        "    config=types.UpdateCachedContentConfig(ttl=\"7200s\")  # 2 hours in seconds\n",
        ")\n",
        "\n",
        "apollo_cache = client.caches.get(name=apollo_cache.name) # Get the updated cache\n",
        "apollo_cache"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "t_PWabuayrf-"
      },
      "source": [
        "## Use the cache for generation\n",
        "\n",
        "As the `CachedContent` object refers to a specific model and parameters, you must create a [`GenerativeModel`](https://ai.google.dev/api/python/google/generativeai/GenerativeModel) using [`from_cached_content`](https://ai.google.dev/api/python/google/generativeai/GenerativeModel#from_cached_content). Then, generate content as you would with a directly instantiated model object."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "metadata": {
        "id": "EG8VNpuIzGwT"
      },
      "outputs": [
        {
          "data": {
            "text/markdown": [
              "There are many lighthearted moments scattered throughout the transcript, often provided by the crew's dry wit and the relatable human touches of everyday life in space.\n",
              "\n",
              "One particularly lighthearted moment occurs when the crew is dealing with an **O2 FLOW HIGH master alarm**. Instead of panicking, Mike Collins offers a rather amusing observation:\n",
              "\n",
              "**02 06 53 37 CMP:** \"That photoelectric cell is a good device. It's worked very well.\"\n",
              "**02 06 53 46 CC:** \"11, Houston. Say again. Over.\"\n",
              "**02 06 53 50 CMP:** \"I say that photoelectric cell amplifier for the master alarm is a good device. It's working very well, and it's a nice pleasing tone.\"\n",
              "**02 06 54 00 CC:** \"Roger. Copy. Thank you.\"\n",
              "**02 06 54 08 CMP:** \"Makes you almost glad to get master alarms.\"\n",
              "\n",
              "This exchange highlights the crew's ability to maintain a sense of humor and composure even when dealing with technical issues and alarms. Mike Collins' comment about being \"almost glad to get master alarms\" due to their \"nice pleasing tone\" is a classic example of gallows humor and makes for a very relatable moment."
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "response = client.models.generate_content(\n",
        "    model=MODEL_ID,\n",
        "    contents='Find a lighthearted moment from this transcript',\n",
        "    config=types.GenerateContentConfig(\n",
        "        cached_content=apollo_cache.name,\n",
        "    )\n",
        ")\n",
        "\n",
        "display(Markdown(response.text))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SzixLQhC3AO2"
      },
      "source": [
        "You can inspect token usage through `usage_metadata`. Note that the cached prompt tokens are included in `prompt_token_count`, but excluded from the `total_token_count`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "metadata": {
        "id": "MLFd8DFZ29lC"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "GenerateContentResponseUsageMetadata(\n",
              "  cache_tokens_details=[\n",
              "    ModalityTokenCount(\n",
              "      modality=<MediaModality.TEXT: 'TEXT'>,\n",
              "      token_count=322698\n",
              "    ),\n",
              "  ],\n",
              "  cached_content_token_count=322698,\n",
              "  candidates_token_count=282,\n",
              "  prompt_token_count=322707,\n",
              "  prompt_tokens_details=[\n",
              "    ModalityTokenCount(\n",
              "      modality=<MediaModality.TEXT: 'TEXT'>,\n",
              "      token_count=322707\n",
              "    ),\n",
              "  ],\n",
              "  thoughts_token_count=4049,\n",
              "  total_token_count=327038\n",
              ")"
            ]
          },
          "execution_count": 32,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "response.usage_metadata"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "metadata": {
        "id": "aXeyCrV56pfk"
      },
      "outputs": [
        {
          "data": {
            "text/markdown": [
              "\n",
              "  As you can see in the `usage_metadata`, the token usage is split between:\n",
              "  *  322698 tokens for the cache,\n",
              "  *  322707 tokens for the input (including the cache, so 9 for the actual prompt),\n",
              "  *  4049 tokens for the thinking process,\n",
              "  *  282 tokens for the output,\n",
              "  *  327038 tokens in total.\n"
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "display(Markdown(f\"\"\"\n",
        "  As you can see in the `usage_metadata`, the token usage is split between:\n",
        "  *  {response.usage_metadata.cached_content_token_count} tokens for the cache,\n",
        "  *  {response.usage_metadata.prompt_token_count} tokens for the input (including the cache, so {response.usage_metadata.prompt_token_count - response.usage_metadata.cached_content_token_count} for the actual prompt),\n",
        "  *  {response.usage_metadata.thoughts_token_count} tokens for the thinking process,\n",
        "  *  {response.usage_metadata.candidates_token_count} tokens for the output,\n",
        "  *  {response.usage_metadata.total_token_count} tokens in total.\n",
        "\"\"\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o-24t14t302N"
      },
      "source": [
        "You can ask new questions of the model, and the cache is reused."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "metadata": {
        "id": "pZngmGj13k9O"
      },
      "outputs": [
        {
          "data": {
            "text/markdown": [
              "The most important part of the transcript, and arguably the entire Apollo 11 mission, is the moment Neil Armstrong steps onto the lunar surface.\n",
              "\n",
              "Here is the quote:\n",
              "\n",
              "**04 13 24 48 CDR (TRANQ) THAT'S ONE SMALL STEP FOR (A) MAN, ONE GIANT LEAP FOR MANKIND.**"
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "chat = client.chats.create(\n",
        "  model=MODEL_ID,\n",
        "  config={\"cached_content\": apollo_cache.name}\n",
        ")\n",
        "\n",
        "response = chat.send_message(message=\"Give me a quote from the most important part of the transcript.\")\n",
        "display(Markdown(response.text))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "metadata": {
        "id": "GhGTutW65u7h"
      },
      "outputs": [
        {
          "data": {
            "text/markdown": [
              "Immediately after his iconic \"one small step\" statement, Neil Armstrong provided detailed observations of the lunar surface:\n",
              "\n",
              "*   He described the **surface** as \"fine and powdery,\" noting that he could pick it up loosely with his toe. It \"adhere[d] in fine layers like powdered charcoal\" to his boots, and he only went in \"a small fraction of an inch, maybe an eighth of an inch,\" clearly seeing his footprints and the treads.\n",
              "*   He commented on the **ease of movement**, stating there was \"no difficulty in moving around,\" finding it \"even perhaps easier than the simulations at one sixth g that we performed in the various simulations on the ground.\" He confirmed it was \"no trouble to walk around.\"\n",
              "*   He observed the **descent engine's impact**, noting it \"did not leave a crater of any size.\" He reported \"about 1 foot clearance on the ground\" and that they were \"essentially on a very level place.\" He saw \"some evidence of rays emanating from the descent engine,\" but described it as \"very insignificant.\"\n",
              "\n",
              "Mission Control (CC) confirmed that they were \"copying\" his observations."
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "response = chat.send_message(\n",
        "    message=\"What was recounted after that?\",\n",
        "    config={\"cached_content\": apollo_cache.name}\n",
        ")\n",
        "display(Markdown(response.text))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "metadata": {
        "id": "SB5Ywx2D6cOn"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "GenerateContentResponseUsageMetadata(\n",
              "  cache_tokens_details=[\n",
              "    ModalityTokenCount(\n",
              "      modality=<MediaModality.TEXT: 'TEXT'>,\n",
              "      token_count=322698\n",
              "    ),\n",
              "  ],\n",
              "  cached_content_token_count=322698,\n",
              "  candidates_token_count=239,\n",
              "  prompt_token_count=322795,\n",
              "  prompt_tokens_details=[\n",
              "    ModalityTokenCount(\n",
              "      modality=<MediaModality.TEXT: 'TEXT'>,\n",
              "      token_count=322795\n",
              "    ),\n",
              "  ],\n",
              "  thoughts_token_count=902,\n",
              "  total_token_count=323936\n",
              ")"
            ]
          },
          "execution_count": 36,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "response.usage_metadata"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "metadata": {
        "id": "sMEzwz6eW-gp"
      },
      "outputs": [
        {
          "data": {
            "text/markdown": [
              "\n",
              "  As you can see in the `usage_metadata`, the token usage is split between:\n",
              "  *  322698 tokens for the cache,\n",
              "  *  322795 tokens for the input (including the cache, so 97 for the actual prompt),\n",
              "  *  902 tokens for the thinking process,\n",
              "  *  239 tokens for the output,\n",
              "  *  323936 tokens in total.\n"
            ],
            "text/plain": [
              "<IPython.core.display.Markdown object>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "display(Markdown(f\"\"\"\n",
        "  As you can see in the `usage_metadata`, the token usage is split between:\n",
        "  *  {response.usage_metadata.cached_content_token_count} tokens for the cache,\n",
        "  *  {response.usage_metadata.prompt_token_count} tokens for the input (including the cache, so {response.usage_metadata.prompt_token_count - response.usage_metadata.cached_content_token_count} for the actual prompt),\n",
        "  *  {response.usage_metadata.thoughts_token_count} tokens for the thinking process,\n",
        "  *  {response.usage_metadata.candidates_token_count} tokens for the output,\n",
        "  *  {response.usage_metadata.total_token_count} tokens in total.\n",
        "\"\"\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7acf6cd63d2d"
      },
      "source": [
        "Since the cached tokens are cheaper than the normal ones, it means this prompt was much cheaper that if you had not used caching. Check the [pricing here](https://ai.google.dev/pricing) for the up-to-date discount on cached tokens."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kfeAxehx79ng"
      },
      "source": [
        "## Delete the cache\n",
        "\n",
        "The cache has a small recurring storage cost (cf. [pricing](https://ai.google.dev/pricing)) so by default it is only saved for an hour. In this case you even set it up for a shorter amont of time (using `\"ttl\"`) of 2h.\n",
        "\n",
        "Still, if you don't need you cache anymore, it is good practice to delete it proactively."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "metadata": {
        "id": "HdP83dj08Nb1"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "cachedContents/0c5j38gpopx49ok6x7kedvbpy65d1bzkq8i5vldr\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "DeleteCachedContentResponse()"
            ]
          },
          "execution_count": 38,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "print(apollo_cache.name)\n",
        "client.caches.delete(name=apollo_cache.name)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7396e8bfdcf8"
      },
      "source": [
        "## Next Steps\n",
        "### Useful API references:\n",
        "\n",
        "If you want to know more about the caching API, you can check the full [API specifications](https://ai.google.dev/api/rest/v1beta/cachedContents) and the [caching documentation](https://ai.google.dev/gemini-api/docs/caching).\n",
        "\n",
        "### Continue your discovery of the Gemini API\n",
        "\n",
        "Check the File API notebook to know more about that API. The [vision capabilities](../quickstarts/Video.ipynb) of the Gemini API are a good reason to use the File API and the caching.\n",
        "The Gemini API also has configurable [safety settings](../quickstarts/Safety.ipynb) that you might have to customize when dealing with big files.\n"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "name": "Caching.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
